package tech.waterism.service;

import cn.hutool.core.util.BooleanUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import tech.waterism.algorithm.Evaluations;
import tech.waterism.constant.DispatchConstants;
import tech.waterism.constant.StationTypeConstans;
import tech.waterism.constant.StringConstants;
import tech.waterism.dto.*;
import tech.waterism.entity.FCurveHc;
import tech.waterism.entity.FPIndex;
import tech.waterism.utils.ModelPlatformUtils;
import tech.waterism.utils.TempDispatchRule;
import tech.waterism.utils.method.DispatchCalculate;
import tech.waterism.utils.method.StatisticsCalculate;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class DispatchService {
    /**
     * dzq
     */
    private static final Set<String> DISPATCH_STORE_SET = new HashSet<>();
    /**
     * x
     */
    private static final Set<String> DISPATCH_STORE_UP_SET = new HashSet<>();
    /**
     * d,r
     */
    private static final Set<String> DISPATCH_DIRECT_SET = new HashSet<>();
    private static final String DZQ = "DZQ";

    static {
        //
        DISPATCH_STORE_SET.add(DZQ);
        //
        DISPATCH_STORE_UP_SET.add("X");

        //
        DISPATCH_DIRECT_SET.add("D");
        DISPATCH_DIRECT_SET.add("R");
    }

    @Autowired
    private ModelPlatformUtils modelPlatformUtils;
    @Autowired
    private DispatchCalculate dispatchCalculate;
    @Autowired
    private StatisticsCalculate statisticsCalculate;
    @Autowired
    private FCurveHcService fCurveHcService;

    /**
     * 调洪演算入口方法（自动填充调度类型）
     *
     * @param dto 调度数据对象
     * @return 计算结果列表（通常只有一个元素）
     * @throws Exception 数据不完整或计算异常
     */
    public List<TopoDataDto> calculate(TopoDataDto dto) throws Exception {
        return calculate(dto, true);
    }

    /**
     * 调洪演算主方法
     * <p>
     * 该方法是调洪演算的统一入口，负责：
     * 1. 数据校验和预处理
     * 2. 初始化分洪排洪数据
     * 3. 填充调度类型到各节点
     * 4. 调用核心计算方法 calTopo()
     * </p>
     *
     * @param dto      调度数据对象，包含拓扑结构、时间参数、调度类型等
     * @param autoFill 是否自动填充调度类型到各节点（通常为 true）
     * @return 计算结果列表，包含完整的流量、水位、库容等时序数据
     * @throws Exception 当 topoObject 为空时抛出"数据不完整"异常
     * @author waterism
     * @see #calTopo(TopoDataDto, Date, Date, Date, Map) 核心计算方法
     */
    public List<TopoDataDto> calculate(TopoDataDto dto, Boolean autoFill) throws Exception {
        // 构建拓扑索引映射：站码 -> 索引位置（用于联调回溯）
        Map<String, Integer> topoIndexMap = new HashMap<>();
        
        // 数据校验：拓扑对象不能为空
        Optional.ofNullable(dto.getTopoObject()).orElseThrow(() -> new Exception("数据不完整"));
        
        // 初始化分洪排洪数据：将所有节点的 QDvr 和 QDsc 清零
        clearDscAndDvr(dto, topoIndexMap, 0);
        
        // 填充调度类型：将 dto 的 dispatchType 复制到各节点
        if (Boolean.TRUE.equals(autoFill)) {
            fillDispatchType(dto);
        }
        
        // VIC 模型检查（分布式水文模型，目前未实现）
        Boolean checkVicResult = checkVic(dto);
        if (BooleanUtil.isTrue(checkVicResult)) {
            // VIC 模型计算路径（未实现）
        } else {
            // 标准调洪演算流程：核心计算方法
            calTopo(dto, dto.getFymdh(), dto.getSymdh(), dto.getMymdh(), topoIndexMap);
        }
        
        // 返回单元素列表（历史原因，保持接口兼容）
        return Collections.singletonList(dto);
    }

    /**
     * 拓扑调洪计算核心方法
     * <p>
     * 该方法是整个调洪演算的核心，负责按拓扑顺序遍历所有节点，依次完成：
     * 1. 预报计算（汇流计算）
     * 2. 调度计算（水库/蓄滞洪区）
     * 3. 统计计算（峰量时等）
     * 4. 联合调度处理
     * </p>
     *
     * @param dto           调度数据对象，包含拓扑节点、联调对象等完整信息
     * @param fymdh         预报起始时间（起调时间）
     * @param symdh         实测起始时间
     * @param mymdh         修改时间（用于指定泄流方式的起始时间）
     * @param topoIndexMap  拓扑节点索引映射（站码 -> 索引位置），用于联调时回溯
     * @throws Exception 数据不完整、计算异常等
     * @author waterism
     * @see #calculate(TopoDataDto, Boolean) 上层调用方法
     * @see DispatchCalculate#resFunction 水库调度
     * @see TempDispatchRule#dispatch 蓄滞洪区调度（硬编码，待优化）
     */
    private void calTopo(TopoDataDto dto, Date fymdh, Date symdh, Date mymdh, Map<String, Integer> topoIndexMap)
            throws Exception {
        // ========== 第一部分：数据准备 ==========
        // 补充水位库容曲线（如果节点缺失则从数据库查询最新曲线）
        compleCurveHc(dto.getTopoObject());
        
        List<TopoObjectDto> topoObject = dto.getTopoObject();
        
        // 联调相关映射：记录哪些节点需要联合调度
        // dispatchMap: 联调组ID -> 上游起点站码列表（用于判断何时触发联调）
        Map<Integer, List<String>> dispatchMap = new HashMap<>();
        // dispatchValueMap: 联调组ID -> 联调对象（包含完整的联调节点信息）
        Map<Integer, DispatchObjectDto> dispatchValueMap = new HashMap<>();
        
        // 记录上游水库站点集合，用于计算到达时间
        Set<FPIndex> rsvrStcds = new HashSet<>();
        
        // 构建联调映射表
        if (dto.getDispatchObjects() != null) {
            dispatchMap = dto.getDispatchObjects().stream()
                    .collect(Collectors.toMap(DispatchObjectDto::getJmsid, DispatchObjectDto::getUpDisNodes));
            dispatchValueMap =
                    dto.getDispatchObjects().stream().collect(Collectors.toMap(DispatchObjectDto::getJmsid, v -> v));
        }
        
        // 枢纽分流数据缓存：记录枢纽各分流口的流量过程
        Map<String, List<ForecastDataDto>> hubDisDataAll = new HashMap<>();
        for (TopoObjectDto index : dto.getTopoObject()) {
            Map<String, List<ForecastDataDto>> hubDisData = index.getFpIndex().getHubDisData();
            if (hubDisData != null) {
                hubDisDataAll.putAll(hubDisData);
            }
        }
        
        // 单站预报成果缓存：站码 -> 预报结果（避免重复计算）
        Map<String, TopoObjectDto> forecastMap = new HashMap<>();
        for (int i = 0; i < topoObject.size(); ) {
            TopoObjectDto topoObjectDto = topoObject.get(i);
            FPIndexDto fpIndex = topoObjectDto.getFpIndex();
            if (forecastMap.containsKey(fpIndex.getStcd())) {
                // 已有预报成果
                fpIndex.setData(forecastMap.get(fpIndex.getStcd()).getFpIndex().getData());
                fpIndex.setEPower(forecastMap.get(fpIndex.getStcd()).getFpIndex().getEPower());
                fpIndex.setStatistics(forecastMap.get(fpIndex.getStcd()).getFpIndex().getStatistics());
                // 分洪排洪判断
                fpIndex.setQDvrIsExist(forecastMap.get(fpIndex.getStcd()).getFpIndex().getQDvrIsExist());
                fpIndex.setQDscIsExist(forecastMap.get(fpIndex.getStcd()).getFpIndex().getQDscIsExist());
                if (forecastMap.get(fpIndex.getStcd()).getFpInputs() != null) {
                    Map<String, List<ForecastDataDto>> inputMap =
                            forecastMap.get(fpIndex.getStcd()).getFpInputs().stream()
                                    .filter(item -> !StringConstants.STORE_ARTP.equals(item.getArtp()))
                                    .collect(Collectors.toMap(FPInputDto::getParcd, FPInputDto::getData));
                    if (topoObjectDto.getFpInputs() != null) {
                        for (FPInputDto fpInput : topoObjectDto.getFpInputs()) {
                            if (inputMap.containsKey(fpInput.getArcd())) {
                                fpInput.setData(inputMap.get(fpInput.getArcd()));
                            }
                        }
                    }
                }
            } else {
                // --- 2.2 新节点，需要完整计算 ---
                List<FPInputDto> fpInputs = topoObject.get(i).getFpInputs();
                
                // === 2.2.1 预报计算：汇流计算（汇总上游来水） ===
                if (fpInputs != null && fpInputs.size() > 0) {
                    for (FPInputDto fpInputDto : fpInputs) {
                        if (fpInputDto.getMdcdList() != null) {
                            // 整理输入分量：从上游节点获取计算结果
                            if (forecastMap.containsKey(fpInputDto.getArcd())) {
                                List<ForecastDataDto> calData =
                                        forecastMap.get(fpInputDto.getArcd()).getFpIndex().getData();
                                
                                // 遍历时间序列，根据不同的入流类型计算流量
                                for (int index = 0; index < fpInputDto.getData().size(); index++) {
                                    ForecastDataDto forecastDataDto = fpInputDto.getData().get(index);
                                    Double foreQsim = 0.0;
                                    
                                    // 根据入流类型（河道D/R、区间面积、水库）计算流量
                                    if (DISPATCH_DIRECT_SET.contains(fpInputDto.getArtp())) {
                                        // 河道直接入流：考虑分洪排洪影响
                                        if (forecastDataDto.getDt().after(fymdh)) {
                                            Double qsim = Optional.ofNullable(calData.get(index).getQSim()).orElse(0.0);
                                            Double qdsc = Optional.ofNullable(calData.get(index).getQDsc()).orElse(0.0);
                                            Double qdvr = Optional.ofNullable(calData.get(index).getQDvr()).orElse(0.0);
                                            foreQsim = qsim + qdsc - qdvr;
                                        } else {
                                            foreQsim = forecastDataDto.getQ();
                                        }
                                    } else if (fpInputDto.getArtp().equals(StringConstants.AREA_ARTP)) {
                                        // 区间面积入流
                                        foreQsim = Optional.ofNullable(calData.get(index).getQSim()).orElse(0.0);
                                    } else if (fpInputDto.getArtp().equals(StringConstants.RSVR_ARTP)) {
                                        // 水库出流
                                        if (forecastDataDto.getDt().after(fymdh)) {
                                            foreQsim = Optional.ofNullable(calData.get(index).getQOut()).orElse(0.0);
                                        } else {
                                            foreQsim = forecastDataDto.getQ();
                                        }
                                    }
                                    
                                    // 扣除取水量
                                    if (fpInputDto.getIntakes() != null) {
                                        for (FPIntakeDto intake : fpInputDto.getIntakes()) {
                                            if (intake.getData() == null || intake.getData().size() == 0) {
                                                continue;
                                            }
                                            foreQsim -=
                                                    Optional.ofNullable(intake.getData().get(index).getQ()).orElse(0.0);
                                        }
                                    }
                                    forecastDataDto.setQ(foreQsim);
                                }
                            }
                            
                            // 处理枢纽分流：如果上游是枢纽，使用枢纽分流数据
                            Integer clen = fpIndex.getClen();
                            if (hubDisDataAll.containsKey(fpInputDto.getArcd())) {
                                List<ForecastDataDto> data = hubDisDataAll.get(fpInputDto.getArcd());
                                Map<Date, ForecastDataDto> dataMap =
                                        data.stream().collect(Collectors.toMap(ForecastDataDto::getDt, v -> v));
                                for (ForecastDataDto datum : fpInputDto.getData()) {
                                    if (!datum.getDt().after(fymdh)) {
                                        continue;
                                    }
                                    if (dataMap.containsKey(datum.getDt())) {
                                        Double q = dataMap.get(datum.getDt()).getQ();
                                        if (q != null) {
                                            datum.setQ(q);
                                        } else if (StringConstants.RSVR_ARTP.equals(fpInputDto.getArtp())) {
                                            datum.setQ(dataMap.get(datum.getDt()).getQOut());
                                        } else {
                                            datum.setQ(dataMap.get(datum.getDt()).getQSim());
                                        }
                                    }
                                }
                            }
                            
                            // 调用预报模型计算（如产汇流模型、马斯京根、等流时线等）
                            for (FMIndexDto fmIndexDto : fpInputDto.getMdcdList()) {
                                ModelTestDto modelTestDto = new ModelTestDto();
                                BeanUtils.copyProperties(fmIndexDto, modelTestDto);
                                modelPlatformUtils.callModelForecast(modelTestDto, fpInputDto.getData(), clen,
                                        fpIndex.getUnt(), symdh, fymdh, fpInputDto.getArcd(), fpInputDto.getArtp(),
                                        false);
                            }
                        }
                    }

                    // 累加所有入流分量，得到断面总入流 QSim
                    fpIndex.getData().forEach(item -> item.setQSim(0.0));
                    for (FPInputDto fpInputDto : fpInputs) {
                        for (int index = 0; index < fpIndex.getData().size(); index++) {
                            Double foreQsim =
                                    Optional.ofNullable(fpInputDto.getData().get(index).getQSim()).orElse(0.0);
                            Double oriQsim = Optional.ofNullable(fpIndex.getData().get(index).getQSim()).orElse(0.0);
                            fpIndex.getData().get(index).setQSim(foreQsim + oriQsim);
                        }
                    }
                    
                    // 叠加基流（地下水补给）
                    List<Double> qbase = fpIndex.getQBases();
                    if (qbase != null && qbase.size() > 0) {
                        List<ForecastDataDto> data = fpIndex.getData();
                        for (int ii = 0; ii < qbase.size() && ii < data.size(); ii++) {
                            data.get(ii).setQSim(data.get(ii).getQSim() + qbase.get(ii));
                        }
                    }
                    
                    // 处理枢纽节点：如果当前节点是枢纽分流口，记录分流数据供下游使用
                    if (fpIndex.getHub() != null && fpIndex.getHub().getGate().stream()
                            .anyMatch(item -> item.getGttp().equals(DispatchConstants.DIS))) {
                        String dtcd = fpIndex.getHub().getGate().stream()
                                .filter(item -> item.getGttp().equals(DispatchConstants.DIS)).toList().get(0).getDtcd();
                        if (!hubDisDataAll.containsKey(dtcd)) {
                            Map<String, List<ForecastDataDto>> hubDisData = new HashMap<>();
                            hubDisData.put(dtcd, fpIndex.getData());
                            fpIndex.setHubDisData(hubDisData);
                            hubDisDataAll.putAll(hubDisData);
                        }
                    }
                    //                    Integer lag = fpIndex.getLag();
                    //                    if (lag != null && lag != 0) {
                    //                        offsetLag(fpIndex);
                    //                    }
                }
                
                // === 2.2.2 调度计算：水库/蓄滞洪区调洪 ===
                if (StationTypeConstans.RSVR_SET.contains(fpIndex.getOutcd())) {
                    // 水库调度：水量平衡/RK4/闸门调度等
                    dispatchCalculate.resFunction(fpIndex, dto.getFymdh(), dto.getMymdh(),
                            topoObjectDto.getDispatchType());
                }
                if (StationTypeConstans.DZQ.equals(fpIndex.getOutcd())) {
                    // 蓄滞洪区调度：⚠️ 硬编码规则，急需改为规则引擎
                    TempDispatchRule.dispatch(fpIndex, dto.getFymdh());
                }
                
                // === 2.2.3 统计计算：峰量时、超警历时等 ===
                DispatchStatisticsDto statistics = statisticsCalculate.calculate(topoObjectDto.getFpIndex(), fymdh,
                        topoObjectDto.getFpIndex().getClen(), topoObjectDto.getFpIndex().getUnt(),
                        topoObjectDto.getFpIndex().getOutcd(), topoObjectDto.getFpIndex().getFloodCharacter(),
                        topoObjectDto.getFpIndex().getCurveHq());
                topoObjectDto.getFpIndex().setStatistics(statistics);
                List<ForecastDataDto> statisticsInfosData =
                        topoObjectDto.getFpIndex().getData().stream().filter(item -> !item.getDt().before(fymdh))
                                .toList();
                List<StatisticsInfoDto> statisticsInfos =
                        Evaluations.refreshStatisticsSum(new ArrayList<>(), statisticsInfosData,
                                topoObjectDto.getFpIndex().getClen(), topoObjectDto.getFpIndex().getUnt(), fymdh, null,
                                null);
                if (!CollectionUtils.isEmpty(statisticsInfos)) {
                    topoObjectDto.getFpIndex().setStatisticsInfo(statisticsInfos.get(0));
                }
                
                // === 2.2.4 计算发电量（水电站） ===
                if (fpIndex.getCurveHc() != null && !fpIndex.getCurveHc().isEmpty()) {
                    List<FCurveHc> curveHc = fpIndex.getCurveHc();
                    fCurveHcService.calculatePower(fpIndex, curveHc, dto.getFymdh());
                }
                
                // 将计算结果缓存，供下游节点使用
                forecastMap.put(fpIndex.getStcd(), topoObjectDto);
            }
            
            // ========== 第三部分：联合调度处理 ==========
            // 判断是否触发联调：当所有上游起点都计算完成时，触发该联调组
            DispatchObjectDto dispatchObjectDto = null;
            Integer tempKey = 0;
            for (Map.Entry<Integer, List<String>> integerListEntry : dispatchMap.entrySet()) {
                if (forecastMap.keySet().containsAll(integerListEntry.getValue())) {
                    dispatchObjectDto = dispatchValueMap.get(integerListEntry.getKey());
                    tempKey = integerListEntry.getKey();
                    break;
                }
            }
            
            // --- 3.1 执行联合调度 ---
            if (dispatchObjectDto != null) {
                // 找到最早的上游节点位置，用于后续回溯
                List<String> upnodes = dispatchMap.get(tempKey);
                int tempI = Integer.MAX_VALUE;
                for (String upnode : upnodes) {
                    tempI = Integer.min(topoIndexMap.get(upnode), tempI);
                }
                
                // 移除已处理的联调组
                dispatchMap.remove(tempKey);
                
                // 联调内部缓存（与外层 forecastMap 独立）
                Map<String, TopoObjectDto> dispatchForecastMap = new HashMap<>();
                
                // 获取联调对象列表
                List<TopoObjectDto> dispatchObject = dispatchObjectDto.getDispatchObject();
                // 初始化联调节点：最上游节点复用已计算的结果
                for (TopoObjectDto objectDto : dispatchObject) {
                    FPIndexDto fpIndexItem = objectDto.getFpIndex();
                    if (upnodes.contains(fpIndexItem.getStcd())) {
                        // 最上游节点，直接复用外层计算结果
                        fpIndexItem.setData(forecastMap.get(fpIndexItem.getStcd()).getFpIndex().getData());
                    }
                }
                
                // 遍历联调节点，逐个计算
                for (int i1 = 0; i1 < dispatchObject.size(); i1++) {
                    TopoObjectDto objectDto = dispatchObject.get(i1);
                    List<FPInputDto> fpInputItem = objectDto.getFpInputs();
                    FPIndexDto fpIndexItem = objectDto.getFpIndex();
                    String outcd = fpIndexItem.getOutcd();
                    
                    // 判断是否需要计算排洪（下游有蓄滞洪区）
                    boolean doCalDsc = false;
                    
                    // 预报计算（联调内部，逻辑与外层类似但使用 dispatchForecastMap）
                    if (fpInputItem != null && fpInputItem.size() > 0) {
                        for (FPInputDto inputDto : fpInputItem) {
                            if (DISPATCH_STORE_UP_SET.contains(inputDto.getArtp())) {
                                doCalDsc = true;
                            }
                            boolean hasMdcdlist = inputDto.getMdcdList() != null;
                            String plcd = inputDto.getPlcd();
                            if (dispatchForecastMap.containsKey(inputDto.getArcd())) {
                                // 在调度关系中
                                String artp = inputDto.getArtp();
                                boolean isDZQ = DZQ.equals(outcd);
                                List<ForecastDataDto> disData =
                                        dispatchForecastMap.get(inputDto.getArcd()).getFpIndex().getData();
                                for (int index = 0; index < disData.size(); index++) {
                                    ForecastDataDto forecastDataDto = inputDto.getData().get(index);
                                    Double calQ = 0.0;
                                    if (DISPATCH_DIRECT_SET.contains(artp)) {
                                        if (forecastDataDto.getDt().after(fymdh)) {
                                            Double qsim = Optional.ofNullable(disData.get(index).getQSim()).orElse(0.0);
                                            Double qdsc = Optional.ofNullable(disData.get(index).getQDsc()).orElse(0.0);
                                            Double qdvr = Optional.ofNullable(disData.get(index).getQDvr()).orElse(0.0);
                                            calQ = qsim + qdsc - qdvr;
                                        } else {
                                            calQ = Optional.ofNullable(disData.get(index).getQReal()).orElse(0.0);
                                        }
                                    } else if (StringConstants.RSVR_ARTP.equals(artp)) {
                                        calQ = Optional.ofNullable(disData.get(index).getQOut()).orElse(0.0);
                                    }
                                    double intake = 0;
                                    if (inputDto.getIntakes() != null) {
                                        for (FPIntakeDto fpIntakeDto : inputDto.getIntakes()) {
                                            if (fpIntakeDto.getData() == null || fpIntakeDto.getData().size() == 0) {
                                                continue;
                                            }
                                            intake += Optional.ofNullable(fpIntakeDto.getData().get(index).getQ())
                                                    .orElse(0.0);
                                        }
                                    }
                                    forecastDataDto.setQ(calQ - intake);
                                    if (!hasMdcdlist && plcd == null) {
                                        if (isDZQ) {
                                            forecastDataDto.setQPrep(calQ);
                                        } else {
                                            forecastDataDto.setQSim(calQ);
                                        }
                                    }
                                }
                            }
                            if (hasMdcdlist) {
                                // forecast
                                Integer clen = fpIndexItem.getClen();
                                for (FMIndexDto fmIndexDto : inputDto.getMdcdList()) {
                                    ModelTestDto modelTestDto = new ModelTestDto();
                                    BeanUtils.copyProperties(fmIndexDto, modelTestDto);
                                    modelPlatformUtils.callModelForecast(modelTestDto, inputDto.getData(), clen,
                                            fpIndexItem.getUnt(), symdh, fymdh, inputDto.getArcd(), inputDto.getArtp(),
                                            false);
                                }
                            }
                        }
                        // 累加qsim,qPrep
                        fpIndexItem.getData().forEach(item -> {
                            item.setQSim(0.0);
                            item.setQPrep(0.0);
                        });
                        for (FPInputDto fpInputDto : fpInputItem) {
                            for (int index = 0; index < fpIndexItem.getData().size(); index++) {
                                Double foreQsim =
                                        Optional.ofNullable(fpInputDto.getData().get(index).getQSim()).orElse(0.0);
                                Double oriQsim =
                                        Optional.ofNullable(fpIndexItem.getData().get(index).getQSim()).orElse(0.0);
                                fpIndexItem.getData().get(index).setQSim(foreQsim + oriQsim);
                                Double foreQprep =
                                        Optional.ofNullable(fpInputDto.getData().get(index).getQPrep()).orElse(0.0);
                                Double oriQprep =
                                        Optional.ofNullable(fpIndexItem.getData().get(index).getQPrep()).orElse(0.0);
                                fpIndexItem.getData().get(index).setQPrep(foreQprep + oriQprep);
                                if (StringConstants.STORE_ARTP.equals(fpInputDto.getArtp())) {
                                    if (dispatchForecastMap.containsKey(fpInputDto.getArcd())) {
                                        Double qDscInput =
                                                Optional.ofNullable(fpInputDto.getData().get(index).getQDsc())
                                                        .orElse(0.0);
                                        Double qDscIndex = Optional.ofNullable(
                                                dispatchForecastMap.get(fpInputDto.getArcd()).getFpIndex().getData()
                                                        .get(index).getQDsc()).orElse(0.0);
                                        fpIndexItem.getData().get(index).setQDsc(qDscInput + qDscIndex);
                                    }
                                }
                            }
                        }
                        List<Double> qbase = fpIndexItem.getQBases();
                        if (qbase != null && qbase.size() > 0) {
                            List<ForecastDataDto> data = fpIndexItem.getData();
                            for (int ii = 0; ii < qbase.size() && ii < data.size(); ii++) {
                                data.get(ii).setQSim(data.get(ii).getQSim() + qbase.get(ii));
                            }
                        }
                        //                        if (fpIndexItem.getLag() != null && fpIndexItem.getLag() != 0) {
                        //                            offsetLag(fpIndexItem);
                        //                        }
                    }
                    
                    // 联调节点调度
                    if (StationTypeConstans.RSVR_SET.contains(outcd)) {
                        // 水库调度
                        calOut(i1, dispatchObject, fymdh, mymdh, dto.getDispatchType());
                    } else if (DZQ.equals(outcd)) {
                        // 蓄滞洪区分洪调度
                        calDvr(i1, dispatchObject, fymdh, mymdh);
                    } else if (doCalDsc) {
                        // 蓄滞洪区排洪调度
                        calDsc(i1, dispatchObject, fymdh, mymdh);
                    }
                    DispatchStatisticsDto statistics = statisticsCalculate.calculate(objectDto.getFpIndex(), fymdh,
                            objectDto.getFpIndex().getClen(), objectDto.getFpIndex().getUnt(),
                            objectDto.getFpIndex().getOutcd(), objectDto.getFpIndex().getFloodCharacter(),
                            objectDto.getFpIndex().getCurveHq());

                    objectDto.getFpIndex().setStatistics(statistics);
                    
                    // 将结果同时放入联调缓存和外层缓存
                    dispatchForecastMap.put(fpIndexItem.getStcd(), objectDto);
                    if (fpIndexItem.getPstcd() != null) {
                        forecastMap.put(fpIndexItem.getPstcd(), objectDto);
                    }
                }
                
                // 联调完成后，回溯到联调起点，将结果赋值回拓扑结构
                // i = topoIndexMap.get(endStcd); // 旧方法：跳到联调末尾
                i = tempI; // 新方法：回到联调起点，保证拓扑顺序
            } else {
                // --- 3.2 非联调节点 ---
                // 移除无效的分洪排洪标记
                removeDvrDscField(topoObject.get(i).getFpIndex());
                i++; // 正常递增
            }
            
            // ========== 第四部分：后处理（到达时间统计） ==========
            // 记录上游水库，用于计算洪水传播到下游河道断面的时间
            // 记录水库站点
            if (StationTypeConstans.RSVR_SET.contains(fpIndex.getOutcd())) {
                FPIndex index = new FPIndex().setStcd(fpIndex.getStcd()).setStnm(fpIndex.getStnm());
                rsvrStcds.add(index);
            }
            
            // 计算河道断面的洪水到达时间（从上游各水库出发）
            if (StationTypeConstans.RIVER_SET.contains(fpIndex.getOutcd()) && rsvrStcds.size() > 0) {
                List<ArriveTimeDto> arriveTimeList = new LinkedList<>();
                for (FPIndex index : rsvrStcds) {
                    String rsvrStcd = index.getStcd();
                    int lag = getLag(topoObject, fpIndex.getStcd(), rsvrStcd);
                    if (fpIndex.getUnt() != null && fpIndex.getUnt().equals("M")) {
                        lag /= 60;
                    }
                    if (lag != 0) {
                        arriveTimeList.add(
                                new ArriveTimeDto().setArriveTime(lag).setStcd(rsvrStcd).setStnm(index.getStnm()));
                    }
                }
                fpIndex.getStatistics().setArriveTimeList(arriveTimeList);
            }
        }
        
        // ========== 第五部分：全局检查和修正 ==========
        // 检查水库调度结果，确保满足约束（如不超蓄、不弃水等）
        dispatchCalculate.resCheck(topoObject, fymdh);
        
        // 检查蓄滞洪区调度结果
        if (dto.getDispatchObjects() != null && dto.getDispatchObjects().size() > 0) {
            dispatchCalculate.storeCheck(dto.getDispatchObjects(), fymdh);
        }
    }

    private void compleCurveHc(List<TopoObjectDto> topoObject) {
        for (TopoObjectDto topoObjectDto : topoObject) {
            if (topoObjectDto.getFpIndex().getCurveHc() == null) {
                // 没有才补充最新一条曲线，确保不会跟反算的指定冲突
                FCurveHc query = new FCurveHc();
                query.setStcd(topoObjectDto.getFpIndex().getStcd());
                FCurveHcDto result = fCurveHcService.getcurveHc(query);
                if (result != null && !result.getCurveHc().isEmpty()) {
                    topoObjectDto.getFpIndex().setCurveHc(result.getCurveHc());
                }
            }
        }
    }

    public void clearDscAndDvr(TopoDataDto dto, Map<String, Integer> topoIndexMap, int startIndex) throws Exception {
        for (int i = startIndex; i < dto.getTopoObject().size(); i++) {
            topoIndexMap.put(dto.getTopoObject().get(i).getFpIndex().getStcd(), i);
            if (dto.getTopoObject().get(i).getFpIndex().getData() == null) {
                throw new Exception(dto.getTopoObject().get(i).getFpIndex().getStnm() + "缺少数据");
            }
            dto.getTopoObject().get(i).getFpIndex().getData().stream().forEach(item0 -> {
                item0.setQDvr(0.0);
                item0.setQDsc(0.0);
            });
        }
        if (dto.getDispatchObjects() != null) {
            for (DispatchObjectDto dispatchObject : dto.getDispatchObjects()) {
                if (dispatchObject.getCalculate() != null && dispatchObject.getCalculate().equals(true)) {
                    for (TopoObjectDto topoObject : dispatchObject.getDispatchObject()) {
                        if (topoObject.getFpIndex().getData() != null) {
                            topoObject.getFpIndex().getData().stream().forEach(item0 -> {
                                item0.setQDvr(0.0);
                                item0.setQDsc(0.0);
                            });
                        }
                    }
                }
            }
        }
    }

    public void removeDvrDscField(FPIndexDto fpIndex) {
        if (fpIndex.getTheFieldRemoved() != null) {
            if (fpIndex.getQDvrIsExist() == null || !fpIndex.getQDvrIsExist()) {
                Map<String, Object> theFieldRemoved_element = new HashMap<>();
                theFieldRemoved_element.put("fieldCode", "qdvr");
                if (!fpIndex.getTheFieldRemoved().contains(theFieldRemoved_element)) {
                    fpIndex.getTheFieldRemoved().add(theFieldRemoved_element);
                }
            }
            if (fpIndex.getQDscIsExist() == null || !fpIndex.getQDscIsExist()) {
                Map<String, Object> theFieldRemoved_element = new HashMap<>();
                theFieldRemoved_element.put("fieldCode", "qdsc");
                if (!fpIndex.getTheFieldRemoved().contains(theFieldRemoved_element)) {
                    fpIndex.getTheFieldRemoved().add(theFieldRemoved_element);
                }
            }
        } else {
            if (fpIndex.getQDvrIsExist() == null || !fpIndex.getQDvrIsExist()) {
                if (fpIndex.getTheFieldRemoved() == null) {
                    fpIndex.setTheFieldRemoved(new ArrayList<>());
                }
                Map<String, Object> theFieldRemoved_element = new HashMap<>();
                theFieldRemoved_element.put("fieldCode", "qdvr");
                if (!fpIndex.getTheFieldRemoved().contains(theFieldRemoved_element)) {
                    fpIndex.getTheFieldRemoved().add(theFieldRemoved_element);
                }
            }
            if (fpIndex.getQDscIsExist() == null || !fpIndex.getQDscIsExist()) {
                if (fpIndex.getTheFieldRemoved() == null) {
                    fpIndex.setTheFieldRemoved(new ArrayList<>());
                }
                Map<String, Object> theFieldRemoved_element = new HashMap<>();
                theFieldRemoved_element.put("fieldCode", "qdsc");
                if (!fpIndex.getTheFieldRemoved().contains(theFieldRemoved_element)) {
                    fpIndex.getTheFieldRemoved().add(theFieldRemoved_element);
                }
            }
        }
    }

    public void calDvr(int index_store, List<TopoObjectDto> dispatchObject, Date fymdh, Date mymdh) {
        List<Integer> indexObjectUpReturnList = new ArrayList<>();
        indexObjectUpReturnList = dispatchCalculate.storeFunction(index_store, null, dispatchObject, fymdh, mymdh,
                DispatchConstants.OPERATE_FLOOD_DIVERSION, indexObjectUpReturnList);
    }

    public void calDsc(int index_downstore, List<TopoObjectDto> dispatchObject, Date fymdh, Date mymdh) {
        List<Integer> indexObjectUpReturnList = new ArrayList<>();
        List<FPInputDto> fpInputs = dispatchObject.get(index_downstore).getFpInputs();
        for (FPInputDto fpInput : fpInputs) {
            for (int k = 0; k < dispatchObject.size(); k++) {
                if (fpInput.getArcd().equals(dispatchObject.get(k).getFpIndex().getStcd()) &&
                        DISPATCH_STORE_UP_SET.contains(fpInput.getArtp())) {
                    indexObjectUpReturnList =
                            dispatchCalculate.storeFunction(k, index_downstore, dispatchObject, fymdh, mymdh,
                                    DispatchConstants.OPERATE_FLOOD_DISCHARGE, indexObjectUpReturnList);
                    DispatchStatisticsDto statistics =
                            statisticsCalculate.calculate(dispatchObject.get(k).getFpIndex(), fymdh,
                                    dispatchObject.get(k).getFpIndex().getClen(),
                                    dispatchObject.get(k).getFpIndex().getUnt(),
                                    dispatchObject.get(k).getFpIndex().getOutcd(),
                                    dispatchObject.get(k).getFpIndex().getFloodCharacter(),
                                    dispatchObject.get(k).getFpIndex().getCurveHq());
                    dispatchObject.get(k).getFpIndex().setStatistics(statistics);
                }
            }
        }
    }

    public void calOut(int index_res, List<TopoObjectDto> dispatchObject, Date fymdh, Date mymdh,
                       Integer dispatchType) {
        dispatchCalculate.resFunction(index_res, dispatchObject, fymdh, mymdh, dispatchType);
    }

    /**
     * 计算到达时间
     *
     * @param constrainStation 下游
     * @param controlStation   上游
     */
    public int getLag(List<TopoObjectDto> topoObject, String constrainStation, String controlStation) throws Exception {
        String tempStcd = controlStation;
        List<List<FMIndexDto>> mdcdList = new LinkedList<>();
        boolean flag = false;
        for (TopoObjectDto topoObjectDto : topoObject) {
            if (topoObjectDto.getFpInputs() == null || topoObjectDto.getFpInputs().size() == 0) {
                continue;
            }
            for (FPInputDto fpInput : topoObjectDto.getFpInputs()) {
                if (fpInput.getArcd().equals(tempStcd)) {
                    mdcdList.add(fpInput.getMdcdList());
                    tempStcd = fpInput.getStcd();
                    if (tempStcd.equals(constrainStation)) {
                        flag = true;
                        break;
                }
            }
        }
            if (flag) {
                break;
            }
        }
        if (!tempStcd.equals(constrainStation)) {
            return 0;
        }
        int sumLag = 0;
        for (List<FMIndexDto> fmIndexDtos : mdcdList) {
            for (FMIndexDto fmIndexDto : fmIndexDtos) {
                // todo 多种模型滞时解析  -- msk、lag(todo)
                if (fmIndexDto.getMdcd().equals("MSK")) {
                    Map<String, String> requestData = new HashMap<>();
                    getModelProperties(requestData, fmIndexDto);
                    int lag = Integer.parseInt(requestData.get("KK")) * Integer.parseInt(requestData.get("MP"));
                    sumLag += lag;
                }
            }
        }
        return sumLag;
        }

    public void getModelProperties(Map<String, String> requestData, FMIndexDto fmIndexDto) throws Exception {
        String mdcd = fmIndexDto.getMdcd();
        ModelTestDto dto = new ModelTestDto();
        BeanUtils.copyProperties(fmIndexDto, dto);
        modelPlatformUtils.generateRequstData(requestData, null, mdcd, dto, null);
    }

    private Boolean checkVic(TopoDataDto dto) {
        return null;
    }

    private void fillDispatchType(TopoDataDto dto) {
        Integer dispatchType = dto.getDispatchType();
        dto.getTopoObject().forEach(i -> i.setDispatchType(dispatchType));
        for (TopoObjectDto topoObjectDto : dto.getTopoObject()) {
            topoObjectDto.getFpIndex().setOutFlowType(dto.getOutFlowType());
        }
        if (dto.getDispatchObjects() == null) {
            return;
        }
        for (DispatchObjectDto dispatchObject : dto.getDispatchObjects()) {
            dispatchObject.getDispatchObject().forEach(i -> i.setDispatchType(dispatchType));
            for (TopoObjectDto topoObjectDto : dispatchObject.getDispatchObject()) {
                topoObjectDto.getFpIndex().setOutFlowType(dto.getOutFlowType());
            }
        }
    }
}
